package com.nft.gateway.app.config;

import com.alibaba.fastjson.JSON;
import com.google.gson.JsonObject;
import com.nft.gateway.app.common.BatchDataStorageService;
import com.nft.gateway.app.entity.OprateInterfaceLog;
import com.nft.utils.ipaddress.IPAddr;
import com.nft.utils.token.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
 * <p>
 * 全局Filter，统一处理会员登录与外部不允许访问的服务
 * </p>
 */
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Value("${spring.profiles.active}")
    private String env;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private BatchDataStorageService batchDataStorageService;

    private static final String AUTH = "Authorization";

    private static final String USERNAME = "username";

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        ServerHttpRequest.Builder mutate = request.mutate();
        String token = request.getHeaders().getFirst(AUTH);

        String id = null;

        if (token != null && !"null".equals(token)) {
            id = JwtUtils.getUserId(token);
            mutate.header("userId", id);
        } else {
            mutate.header("userId", "0");
        }

        String redisTokens = (String) redisTemplate.opsForValue().get("userToken-app:" + id);

        if(redisTokens==null){
            redisTokens="";
        }

        if (!redisTokens.equals(token)) {
            mutate.header("userId", "0");
        }

        //开发环境不需要鉴权
        if ("dev".equals(env)) {
            return chain.filter(exchange);
        }
        try {
            //api接口，校验用户必须登录
            if (matchPath(path)) {
                String username = request.getHeaders().getFirst(USERNAME);
//            String token = request.getHeaders().getFirst(AUTH);
                ServerHttpResponse response = exchange.getResponse();
//            response.setStatusCode(HttpStatus.OK);
                if (StringUtils.isBlank(token)) {
                    log.error("token not found");
                    return out(response);
                }

                String userId = JwtUtils.getUserId(token);
                Boolean isCheck = JwtUtils.verify(token, username, userId);
                if (!isCheck) {
                    log.error("invalid token");
                    return out(response);
                }
                Object redisTokenObject = redisTemplate.opsForValue().get("userToken-app:" + userId);
                if (redisTokenObject == null) {
                    log.error("redisToken not found");
                    return out(response);
                }
                String redisToken = redisTokenObject.toString();
                if (redisToken == null || (redisToken != null && !redisToken.equals(token))) {
                    log.error("redisToken not found");
                    return out(response);
                }

                // TODO 将用户信息存放在请求header中传递给下游业务
                mutate.header("platCode", "app");
                mutate.header("username", username);
                mutate.header("userId", userId);
                ServerHttpRequest buildReuqest = mutate.build();
                //todo 如果响应中需要放数据，也可以放在response的header中
                response.getHeaders().add(USERNAME, username);
                insertLog(path, exchange);
                return chain.filter(exchange.mutate()
                        .request(buildReuqest)
                        .response(response)
                        .build());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //内部服务接口，不允许外部访问
        if (antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response);
        }
        return chain.filter(exchange.mutate().build());
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Mono<Void> out(ServerHttpResponse response) {
        JsonObject message = new JsonObject();
        message.addProperty("success", Boolean.FALSE);
        message.addProperty("code", "401");
        message.addProperty("data", "鉴权失败");
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    public Boolean matchPath(String path) {///**
        if (antPathMatcher.match("/api/**/v2/api-docs", path) ||
                antPathMatcher.match("/api/nft-school/auth/getPhoneCode", path) ||
                antPathMatcher.match("/api/nft-school/auth/loginByCode", path) ||
                antPathMatcher.match("/api/nft-school/auth/getopenid", path) ||
                antPathMatcher.match("/api/nft-school/auth/logout", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getSchoolList", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getSchoolInfo", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getNoticeList", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getNoticeInfo", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getUserAppInfoByPhone", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getUserAppInfoByUserId", path) ||
                antPathMatcher.match("/api/nft-school/userApp/updateUserAppInfo", path) ||
                antPathMatcher.match("/api/nft-school/userApp/realNameUser", path) ||
                antPathMatcher.match("/api/nft-school/userApp/updateUserAppPhone", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getUserSchoolListByPhone", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getUserClassesListByUserId", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getPayRecords", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getPayRemainder", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getConsume", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getXsConsume", path) ||
                antPathMatcher.match("/api/nft-school/userApp/realConsume", path) ||
                antPathMatcher.match("/api/nft-school/userApp/getXsList", path)||
                antPathMatcher.match("/api/nft-school/userApp/getSchoolAllList", path)||
                antPathMatcher.match("/api/nft-school/book/getGrade", path)||
                antPathMatcher.match("/api/nft-school/book/getSubject", path)||
                antPathMatcher.match("/api/nft-school/book/getDispart", path)||
                antPathMatcher.match("/api/nft-school/book/list", path)||
                antPathMatcher.match("/api/nft-school/book/get", path)||
                antPathMatcher.match("/api/nft-school/book/getDetail", path)||
                antPathMatcher.match("/api/nft-school/book/getDetailJx", path)||
                antPathMatcher.match("/api/nft-school/book/saveWorkPhone", path)||
                antPathMatcher.match("/api/nft-school/book/getBook", path)||
                antPathMatcher.match("/api/nft-school/book/putBook", path)||
                antPathMatcher.match("/api/nft-school/book/getClass", path)||
                antPathMatcher.match("/api/nft-school/book/putClass", path)||
                antPathMatcher.match("/api/nft-school/book/putUserName", path)||
                antPathMatcher.match("/api/nft-school/book/putAvatar", path)||
                antPathMatcher.match("/api/nft-school/book/getPhone", path)||
                antPathMatcher.match("/api/nft-school/book/putOpenid", path)||
                antPathMatcher.match("/api/nft-school/book/wxUser", path)||
                antPathMatcher.match("/api/nft-school/book/getErrorQ", path)) {
            return false;
        }
        return true;
    }

    public void insertLog(String path, ServerWebExchange exchange) {

        try {
            ServerHttpRequest request = exchange.getRequest();
            OprateInterfaceLog oprateInterfaceLog = new OprateInterfaceLog();
            oprateInterfaceLog.setNode(path);
            if ("POST".equals(request.getMethod().name())) {
                Flux<DataBuffer> cachedBody = exchange.getAttribute("cachedRequestBodyObject");
                if (cachedBody != null) {
                    String raw = toRaw(cachedBody);
                    oprateInterfaceLog.setContent(raw);
                }
            } else if ("GET".equals(request.getMethod().name())) {
                oprateInterfaceLog.setContent(JSON.toJSONString(request.getQueryParams()));
            }
            oprateInterfaceLog.setGeoip(IPAddr.getLocalHostLANAddress().getHostAddress());
            oprateInterfaceLog.setUsername(request.getHeaders().getFirst(USERNAME));
            oprateInterfaceLog.setAction("APP");
            batchDataStorageService.saveRealTimeData(oprateInterfaceLog);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

    }

    /**
     * 用于获取请求参数
     *
     * @param body
     * @return
     */
    private static String toRaw(Flux<DataBuffer> body) {
        AtomicReference<String> rawRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            rawRef.set(Strings.fromUTF8ByteArray(bytes));
        });
        return rawRef.get();
    }
}
